package network

import (
	"go.uber.org/zap"
	"net"
	"sync"
	"time"
)

var log, _ = zap.NewDevelopment()

type TCPServer struct {
	Addr            string
	MaxConnNum      int
	PendingWriteNum int
	NewAgent        func(*TCPConn) Agent
	ln              net.Listener
	connSet         ConnSet
	connSetMutex    sync.Mutex
	wgLn            sync.WaitGroup
	connWaitGroup   sync.WaitGroup

	// msg parser
	LenMsgLen    int
	LittleEndian bool
	msgParser    *MsgParser
}

func (server *TCPServer) Start() {
	server.init()
	go server.run()
}

func (server *TCPServer) init() {
	ln, err := net.Listen("tcp", server.Addr)
	if err != nil {
		log.Fatal("err:", zap.Error(err))
	}

	if server.MaxConnNum <= 0 {
		server.MaxConnNum = 5000
		log.Info("invalid MaxConnNum, reset to  ", zap.Int("MaxConnNum", server.MaxConnNum))
	}
	if server.PendingWriteNum <= 0 {
		server.PendingWriteNum = 500
		log.Info("invalid PendingWriteNum, reset to ", zap.Int("PendingWriteNum", server.PendingWriteNum))
	}
	if server.NewAgent == nil {
		log.Fatal("CreateAgent must not be nil")
	}

	server.ln = ln
	server.connSet = make(ConnSet)

	// msg parser
	msgParser := NewMsgParser()
	server.msgParser = msgParser
}

func (server *TCPServer) run() {
	server.wgLn.Add(1)
	defer server.wgLn.Done()

	var tempDelay time.Duration
	for {
		conn, err := server.ln.Accept()
		if err != nil {
			if ne, ok := err.(net.Error); ok && ne.Timeout() {
				if tempDelay == 0 {
					tempDelay = 5 * time.Millisecond
				} else {
					tempDelay *= 2
				}
				if max := 1 * time.Second; tempDelay > max {
					tempDelay = max
				}
				log.Info("accept error:", zap.Error(err), zap.Duration("retrying in", tempDelay))
				time.Sleep(tempDelay)
				continue
			}
			return
		}
		tempDelay = 0

		server.connSetMutex.Lock()
		if len(server.connSet) >= server.MaxConnNum {
			server.connSetMutex.Unlock()
			err := conn.Close()
			if err != nil {
				log.Error("close net.Conn err:", zap.Error(err))
			}
			log.Debug("too many connections")
			continue
		}
		server.connSet[conn] = struct{}{}
		server.connSetMutex.Unlock()

		server.connWaitGroup.Add(1)

		tcpConn := newTCPConn(conn, server.PendingWriteNum, server.msgParser)
		agent := server.NewAgent(tcpConn)
		go func() {
			agent.Run()

			// cleanup
			tcpConn.Close()
			server.connSetMutex.Lock()
			delete(server.connSet, conn)
			server.connSetMutex.Unlock()
			agent.OnClose()

			server.connWaitGroup.Done()
		}()
	}
}

func (server *TCPServer) Close() {
	err := server.ln.Close()
	if err != nil {
		log.Error("close server.ln err:", zap.Error(err))
	}
	server.wgLn.Wait()

	server.connSetMutex.Lock()
	for conn := range server.connSet {
		err := conn.Close()
		if err != nil {
			log.Error("close net.Conn err:", zap.Error(err))
		}
	}
	server.connSet = nil
	server.connSetMutex.Unlock()
	server.connWaitGroup.Wait()
}
